home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / C⁄C++ / Xconq 7.0d37 / source / kernel / nlang.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-05-02  |  39.1 KB  |  1,662 lines  |  [TEXT/KAHL]

  1. /* Interface-independent natural language handling for Xconq.
  2.    Copyright (C) 1987, 1988, 1989, 1991, 1992, 1993, 1994, 1995
  3.    Stanley T. Shebs.
  4.  
  5. Xconq is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2, or (at your option)
  8. any later version.  See the file COPYING.  */
  9.  
  10. /* This file should be entirely replaced for non-English Xconq. */
  11. /* (One way to do this would be to call this file "nlang-en.c", then
  12.    symlink this or nlang-fr.c, etc to nlang.c when configuring;
  13.    similarly for help.c.) */
  14.  
  15. #include "conq.h"
  16.  
  17. static char *usual_date_string PROTO ((int date));
  18.  
  19. static int gain_count PROTO ((Side *side, int u, int r));
  20. static int loss_count PROTO ((Side *side, int u, int r));
  21. static int atkstats PROTO ((Side *side, int a, int d));
  22. static int hitstats PROTO ((Side *side, int a, int d));
  23.  
  24. int basehour;
  25. int baseday = -1;
  26. int basemonth = 0;
  27. int baseyear = 1900;
  28.  
  29. /* Short names of directions. */
  30.  
  31. char *dirnames[] = DIRNAMES;
  32.  
  33. char *unitbuf = NULL;
  34.  
  35. char *past_unitbuf = NULL;
  36.  
  37. static char *side_short_title = NULL;
  38.  
  39. #define NUMGAINREASONS 3
  40.  
  41. static char *gain_reason_names[] = { "Ini", "Bld", "Cap" };
  42.  
  43. #define NUMLOSSREASONS 3
  44.  
  45. static char *loss_reason_names[] = { "Cbt", "Stv", "Dis" };
  46.  
  47. /* (should use an enum for date step names) */
  48.  
  49. int calendartype = -1;
  50.  
  51. char *datebuf = NULL;
  52.  
  53. char *turnname;
  54. char *datestepname;
  55. int datestep;
  56.  
  57. char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  58.            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" };
  59.  
  60. /* This is the number of types to mention by name; any others will
  61.    just be included in the count of missing images. */
  62.  
  63. #define NUMTOLIST 5
  64.  
  65. static char *missinglist = NULL;
  66.  
  67. /* This array allows for counting up to 4 classes of missing images. */
  68.  
  69. static int missing[4];
  70.  
  71. static int totlisted = 0;
  72.  
  73. /* Pad a given buffer with blanks out to the given position. */
  74.  
  75. void
  76. pad_out(buf, n)
  77. char *buf;
  78. int n;
  79. {
  80.     int i, len = strlen(buf);
  81.  
  82.     if (n < 1)
  83.       return;
  84.     for (i = len; i < n; ++i) {
  85.     buf[i] = ' ';
  86.     }
  87.     buf[n - 1] = '\0';
  88. }
  89.  
  90. char *
  91. short_side_title(side)
  92. Side *side;
  93. {
  94.     if (side_short_title == NULL)
  95.       side_short_title = xmalloc(BUFSIZE);
  96.     if (side == NULL) {
  97.     return " - ";
  98.     } else if (side->name) {
  99.     return side->name;
  100.     } else if (side->pluralnoun) {
  101.     sprintf(side_short_title, "The %s", side->pluralnoun);
  102.     } else if (side->noun) {
  103.     sprintf(side_short_title, "The %s", plural_form(side->noun));
  104.     } else if (side->adjective) {
  105.     sprintf(side_short_title, "The %s side", side->adjective);
  106.     } else {
  107.     return " - ";
  108.     }
  109.     return side_short_title;
  110. }
  111.  
  112. char *
  113. shortest_side_title(side2, buf)
  114. Side *side2;
  115. char *buf;
  116. {
  117.     if (side2 == NULL) {
  118.     return "-";
  119.     } else if (side2->name) {
  120.     return side2->name;
  121.     } else if (side2->adjective) {
  122.     return side2->adjective;
  123.     } else if (side2->noun) {
  124.     return side2->noun;
  125.     } else {
  126.     sprintf(buf, "(#%d)", side_number(side2));
  127.     }
  128.     return buf;
  129. }
  130.  
  131. /* (should let NULL observer be assumed objective observer) */
  132.  
  133. char *
  134. rel_short_side_title(side, side2, n)
  135. Side *side, *side2;
  136. int n;
  137. {
  138.     return "???";
  139. }
  140.  
  141. char *
  142. rel_long_side_title(side, side2)
  143. Side *side, *side2;
  144. {
  145.     return "???";
  146. }
  147.  
  148. char *
  149. narrative_side_desc(side, side2)
  150. Side *side, *side2;
  151. {
  152.     return "???";
  153. }
  154.  
  155. char *
  156. narrative_side_desc_first(side, side2)
  157. Side *side, *side2;
  158. {
  159.     return "???";
  160. }
  161.  
  162. char *
  163. long_player_title(buf, player, thisdisplayname)
  164. char *buf, *thisdisplayname;
  165. Player *player;
  166. {
  167.     if (player == NULL) {
  168.     sprintf(buf, "");
  169.     } else if (player->displayname != NULL) {
  170.     if (thisdisplayname != NULL && strcmp(player->displayname, thisdisplayname) == 0) {
  171.         sprintf(buf, "You");
  172.     } else {
  173.         sprintf(buf, "%s", player->displayname);
  174.     }
  175.     if (player->aitypename != NULL) {
  176.         tprintf(buf, "(+ AI %s)", player->aitypename);
  177.     }
  178.     } else if (player->aitypename != NULL) {
  179.     sprintf(buf, "AI %s", player->aitypename);
  180.     } else {
  181.     sprintf(buf, "-");
  182.     }
  183.     return buf;
  184. }
  185.  
  186. char *
  187. short_player_title(buf, player, thisdisplayname)
  188. char *buf, *thisdisplayname;
  189. Player *player;
  190. {
  191.     return long_player_title(buf, player, thisdisplayname);
  192. }
  193.  
  194. void
  195. side_and_type_name(buf, side, u, side2)
  196. char *buf;
  197. Side *side, *side2;
  198. int u;
  199. {
  200.     /* Decide how to identify the side. */
  201.     if (side2 == NULL) {
  202.     sprintf(buf, "independent ");
  203.     } else if (side == side2) {
  204.     sprintf(buf, "your ");
  205.     } else {
  206.     /* (this could be more elaborate) */
  207.     sprintf(buf, "%s ",
  208.         (side2->adjective ? side2->adjective :
  209.          (side2->noun ? side2->noun : "?")));
  210.     }
  211.     /* Glue the pieces together and return it. */
  212.     strcat(buf, u_type_name(u));
  213. }
  214.  
  215. /* Build a short phrase describing a given unit to a given side,
  216.    basically consisting of indication of unit's side, then of unit itself. */
  217.  
  218. char *
  219. unit_handle(side, unit)
  220. Side *side;
  221. Unit *unit;
  222. {
  223.     char *utypename;
  224.     Side *side2;
  225.  
  226.     if (unitbuf == NULL)
  227.       unitbuf = xmalloc(BUFSIZE);
  228.     /* Handle various weird situations. */
  229.     if (unit == NULL)
  230.       return "???";
  231.     if (!alive(unit)) {
  232.         sprintf(unitbuf, "dead #%d", unit->id);
  233.         return unitbuf;
  234.     }
  235.     /* If this unit represents "yourself", say so. */
  236.     if (side != NULL && unit == side->selfunit)
  237.       return "you";
  238. #if 0 /* how to do these days? */
  239.     /* Sometimes a unit's description should be its name alone. */
  240.     if (u_name_format(unit->type) == 2 && unit->name)
  241.       return unit->name;
  242. #endif
  243.     /* Decide how to identify the side. */
  244.     side2 = unit->side;
  245.     if (side2 == NULL) {
  246.     sprintf(unitbuf, "the ");
  247.     } else if (side2 == side) {
  248.     sprintf(unitbuf, "your ");
  249.     } else {
  250.     sprintf(unitbuf, "the %s ",
  251.         (side2->adjective ? side2->adjective :
  252.          (side2->noun ? side2->noun : side_desig(side2))));
  253.     }
  254.     /* Now add the unit's unique description. */
  255.     /* (Should this ever use short name?) */
  256.     utypename = u_type_name(unit->type);
  257.     if (unit->name) {
  258.     tprintf(unitbuf, "%s %s", utypename, unit->name);
  259.     } else if (unit->number > 0) {
  260.     tprintf(unitbuf, "%d%s %s",
  261.         unit->number, ordinal_suffix(unit->number), utypename);
  262.     } else {
  263.     tprintf(unitbuf, "%s", utypename);
  264.     }
  265.     return unitbuf;
  266. }
  267.  
  268. /* Shorter unit description omits side name, but uses same buffer. */
  269.  
  270. char *
  271. short_unit_handle(unit)
  272. Unit *unit;
  273. {
  274.     int u;
  275.  
  276.     if (unitbuf == NULL)
  277.       unitbuf = xmalloc(BUFSIZE);
  278.     if (unit == NULL)
  279.       return "???";
  280.     if (!alive(unit)) {
  281.         sprintf(unitbuf, "dead #%d", unit->id);
  282.         return unitbuf;
  283.     }
  284.     u = unit->type;
  285.     if (!empty_string(unit->name)) {
  286.     sprintf(unitbuf, "%s", unit->name);
  287.     } else if (!empty_string(u_short_name(u))) {
  288.     sprintf(unitbuf, "%d%s %s",
  289.         unit->number, ordinal_suffix(unit->number), u_short_name(u));
  290.     } else {
  291.     sprintf(unitbuf, "%d%s %s",
  292.         unit->number, ordinal_suffix(unit->number), u_type_name(u));
  293.     }
  294.     return unitbuf;
  295. }
  296.  
  297. void
  298. name_or_number(unit, buf)
  299. Unit *unit;
  300. char *buf;
  301. {
  302.     if (unit->name) {
  303.     sprintf(buf, "%s", unit->name);
  304.     } else if (unit->number > 0) {
  305.     sprintf(buf, "%d%s", unit->number, ordinal_suffix(unit->number));
  306.     } else {
  307.     sprintf(buf, "");
  308.     }
  309. }
  310.  
  311. /* Build a short phrase describing a given past unit to a given side,
  312.    basically consisting of indication of unit's side, then of unit itself. */
  313.  
  314. char *
  315. past_unit_handle(side, past_unit)
  316. Side *side;
  317. PastUnit *past_unit;
  318. {
  319.     char *utypename;
  320.     Side *side2;
  321.  
  322.     if (past_unitbuf == NULL)
  323.       past_unitbuf = xmalloc(BUFSIZE);
  324.     /* Handle various weird situations. */
  325.     if (past_unit == NULL)
  326.       return "???";
  327.     /* Decide how to identify the side. */
  328.     side2 = past_unit->side;
  329.     if (side2 == NULL) {
  330.     sprintf(past_unitbuf, "the ");
  331.     } else if (side2 == side) {
  332.     sprintf(past_unitbuf, "your ");
  333.     } else {
  334.     sprintf(past_unitbuf, "the %s ",
  335.         (side2->adjective ? side2->adjective :
  336.          (side2->noun ? side2->noun : side_desig(side2))));
  337.     }
  338.     /* Now add the past_unit's unique description. */
  339.     /* (Should this ever use short name?) */
  340.     utypename = u_type_name(past_unit->type);
  341.     if (past_unit->name) {
  342.     tprintf(past_unitbuf, "%s %s", utypename, past_unit->name);
  343.     } else if (past_unit->number > 0) {
  344.     tprintf(past_unitbuf, "%d%s %s",
  345.         past_unit->number, ordinal_suffix(past_unit->number), utypename);
  346.     } else {
  347.     tprintf(past_unitbuf, "%s", utypename);
  348.     }
  349.     return past_unitbuf;
  350. }
  351.  
  352. /* Shorter past_unit description omits side name, but uses same buffer. */
  353.  
  354. char *
  355. short_past_unit_handle(past_unit)
  356. PastUnit *past_unit;
  357. {
  358.     int u;
  359.  
  360.     if (past_unitbuf == NULL)
  361.       past_unitbuf = xmalloc(BUFSIZE);
  362.     if (past_unit == NULL)
  363.       return "???";
  364.     u = past_unit->type;
  365.     if (!empty_string(past_unit->name)) {
  366.     sprintf(past_unitbuf, "%s", past_unit->name);
  367.     } else if (!empty_string(u_short_name(u))) {
  368.     sprintf(past_unitbuf, "%d%s %s",
  369.         past_unit->number, ordinal_suffix(past_unit->number), u_short_name(u));
  370.     } else {
  371.     sprintf(past_unitbuf, "%d%s %s",
  372.         past_unit->number, ordinal_suffix(past_unit->number), u_type_name(u));
  373.     }
  374.     return past_unitbuf;
  375. }
  376.  
  377. void
  378. past_name_or_number(past_unit, buf)
  379. PastUnit *past_unit;
  380. char *buf;
  381. {
  382.     if (past_unit->name) {
  383.     sprintf(buf, "%s", past_unit->name);
  384.     } else if (past_unit->number > 0) {
  385.     sprintf(buf, "%d%s", past_unit->number, ordinal_suffix(past_unit->number));
  386.     } else {
  387.     sprintf(buf, "");
  388.     }
  389. }
  390.  
  391. /* Given a unit and optional type u, summarize construction status
  392.    and timing. */
  393.  
  394. void
  395. construction_desc(buf, unit, u)
  396. char *buf;
  397. Unit *unit;
  398. int u;
  399. {
  400.     int est, u2;
  401.     char ubuf[10], tmpbuf[100];
  402.     Task *task;
  403.     Unit *unit2;
  404.  
  405.     if (u != NONUTYPE) {
  406.     est = est_completion_time(unit, u);
  407.     if (est >= 0) {
  408.         sprintf(ubuf, "[%2d] ", est);
  409.     } else {
  410.         strcpy(ubuf, " --  ");
  411.     }
  412.     } else {
  413.     strcpy(ubuf, "");
  414.     }
  415.     name_or_number(unit, tmpbuf);
  416.     sprintf(buf, "%s%s %s", ubuf, u_type_name(unit->type), tmpbuf);
  417.     pad_out(buf, 20);
  418.     if (unit->plan
  419.     && unit->plan->tasks) {
  420.       task = unit->plan->tasks;
  421.       if (task->type == TASK_BUILD) {
  422.     u2 = task->args[0];
  423.     tprintf(buf, " %s ", (is_unit_type(u2) ? u_type_name(u2) : "?"));
  424.     unit2 = find_unit(task->args[1]);
  425.     if (in_play(unit2) && unit2->type == u2) {
  426.         tprintf(buf, "%d/%d done ", unit2->cp, u_cp(unit2->type));
  427.     }
  428.     tprintf(buf, "(%d of %d)", task->args[2] + 1, task->args[3]);
  429.       } else if (task->type == TASK_RESEARCH) {
  430.     u2 = task->args[0];
  431.         if (is_unit_type(u2)) {
  432.         tprintf(buf, " %s tech %d/%d",
  433.             u_type_name(u2), unit->side->tech[u2], task->args[1]);
  434.         }
  435.       }
  436.     }
  437. }
  438.  
  439. void
  440. constructible_desc(buf, side, u, unit)
  441. char *buf;
  442. Side *side;
  443. int u;
  444. Unit *unit;
  445. {
  446.     char estbuf[20];
  447.     char techbuf[50];
  448.     int est, tp, num;
  449.  
  450.     if (unit != NULL) {
  451.     est = est_completion_time(unit, u);
  452.         if (est >= 0) {
  453.         sprintf(estbuf, "[%2d] ", est);
  454.         if (uu_tp_to_build(unit->type, u) > 0) {
  455.         tp = (unit->tooling ? unit->tooling[u] : 0);
  456.         tprintf(estbuf, "(%2d) ", tp);
  457.         }
  458.         } else {
  459.         strcpy(estbuf, " --  ");
  460.         }
  461.     } else {
  462.         strcpy(estbuf, "");
  463.     }
  464.     if (u_tech_max(u) > 0) {
  465.         sprintf(techbuf, "[T %d/%d/%d] ", side->tech[u], u_tech_to_build(u), u_tech_max(u));
  466.     } else {
  467.         strcpy(techbuf, "");
  468.     }
  469.     sprintf(buf, "%s%s%-16.16s", estbuf, techbuf, u_type_name(u));
  470.     num = num_units_in_play(side, u);
  471.     if (num > 0) {
  472.     tprintf(buf, "  %3d", num);
  473.     }
  474.     num = num_units_incomplete(side, u);
  475.     if (num > 0) {
  476.     tprintf(buf, "(%d)", num);
  477.     }
  478. }
  479.  
  480. int
  481. est_completion_time(unit, u2)
  482. Unit *unit;
  483. int u2;
  484. {
  485.     if (uu_acp_to_create(unit->type, u2) < 1)
  486.       return (-1);
  487.     return normal_completion_time(unit->type, u2);
  488. }
  489.  
  490. void
  491. historical_event_date_desc(hevt, buf)
  492. HistEvent *hevt;
  493. char *buf;
  494. {
  495.     sprintf(buf, "%d: ", hevt->startdate);
  496. }
  497.  
  498. int find_event_type PROTO ((Obj *sym));
  499.  
  500. int
  501. find_event_type(sym)
  502. Obj *sym;
  503. {
  504.     int i;
  505.  
  506.     for (i = 0; hevtdefns[i].name != NULL; ++i) {
  507.     if (strcmp(c_string(sym), hevtdefns[i].name) == 0)
  508.       return i;
  509.     }
  510.     return -1;
  511. }
  512.  
  513. /* (should reindent) */
  514. void
  515. historical_event_desc(side, hevt, buf)
  516. Side *side;
  517. HistEvent *hevt;
  518. char *buf;
  519. {
  520.     int data0 = hevt->data[0];
  521.     int data1 = hevt->data[1];
  522.     Obj *rest, *pattern, *text;
  523.     Unit *unit, *unit2;
  524.     PastUnit *pastunit, *pastunit2;
  525.     Side *side2;
  526.     
  527.     if (g_event_messages() != lispnil) {
  528.     for (rest = g_event_messages(); rest != lispnil; rest = cdr(rest)) {
  529.         if (consp(car(rest))) {
  530.         pattern = car(car(rest));
  531.         if (symbolp(pattern)
  532.             && find_event_type(pattern) == hevt->type) {
  533.             text = cadr(car(rest));
  534.             sprintf(buf, c_string(text));
  535.             return;
  536.         } else if (consp(pattern)
  537.                && symbolp(car(pattern))
  538.                && find_event_type(car(pattern)) == hevt->type
  539.                /* && args match pattern args */
  540.                ) {
  541.             text = cadr(car(rest));
  542.             sprintf(buf, c_string(text));
  543.             return;
  544.         }
  545.         }
  546.     }
  547.     }
  548.     /* Generate a default description of the event. */
  549.     switch (hevt->type) {
  550.       case H_LOG_STARTED:
  551.     sprintf(buf, "we started recording events");
  552.     break;
  553.       case H_LOG_ENDED:
  554.     sprintf(buf, "we stopped recording events");
  555.     break;
  556.       case H_GAME_STARTED:
  557.     sprintf(buf, "we started the game");
  558.     break;
  559.       case H_GAME_SAVED:
  560.     sprintf(buf, "we saved the game");
  561.     break;
  562.       case H_GAME_RESTARTED:
  563.     sprintf(buf, "we restarted the game");
  564.     break;
  565.       case H_GAME_ENDED:
  566.     sprintf(buf, "we ended the game");
  567.     break;
  568.       case H_SIDE_JOINED:
  569.           side2 = side_n(data0);
  570.     sprintf(buf, "%s joined the game",
  571.         (side == side2 ? "you" : side_name(side2)));
  572.     break;
  573.       case H_SIDE_LOST:
  574.           side2 = side_n(data0);
  575.     sprintf(buf, "%s lost!", (side == side2 ? "you" : side_name(side2)));
  576.     /* Include an explanation of the cause, if there is one. */
  577.     if (data1 == -1) {
  578.         tprintf(buf, " (resigned)");
  579.     } else if (data1 == -2) {
  580.         tprintf(buf, " (self-unit died)");
  581.     } else if (data1 > 0) {
  582.         tprintf(buf, " (scorekeeper %d)", data1);
  583.     } else {
  584.         tprintf(buf, " (don't know why)");
  585.     }
  586.     break;
  587.       case H_SIDE_WON:
  588.           side2 = side_n(data0);
  589.     sprintf(buf, "%s won!", (side == side2 ? "you" : side_name(side2)));
  590.     /* Include an explanation of the cause, if there is one. */
  591.     if (data1 > 0) {
  592.         tprintf(buf, " (scorekeeper %d)", data1);
  593.     } else {
  594.         tprintf(buf, " (don't know why)");
  595.     }
  596.     break;
  597.       case H_UNIT_CREATED:
  598.           side2 = side_n(data0);
  599.     sprintf(buf, "%s created ",
  600.         (side == side2 ? "you" : side_name(side2)));
  601.     unit = find_unit(data1);
  602.     if (unit != NULL) {
  603.       tprintf(buf, "%s", unit_handle(side, unit));
  604.     } else {
  605.         pastunit = find_past_unit(data1);
  606.         if (pastunit != NULL) {
  607.         tprintf(buf, "%s", past_unit_handle(side, pastunit));
  608.         } else {
  609.         tprintf(buf, "%d??", data1);
  610.         }
  611.     }
  612.     break;
  613.       case H_UNIT_COMPLETED:
  614.           side2 = side_n(data0);
  615.     sprintf(buf, "%s completed ",
  616.         (side == side2 ? "you" : side_name(side2)));
  617.     unit = find_unit(data1);
  618.     if (unit != NULL) {
  619.       tprintf(buf, "%s", unit_handle(side, unit));
  620.     } else {
  621.         pastunit = find_past_unit(data1);
  622.         if (pastunit != NULL) {
  623.         tprintf(buf, "%s", past_unit_handle(side, pastunit));
  624.         } else {
  625.         tprintf(buf, "%d??", data1);
  626.         }
  627.     }
  628.     break;
  629.       case H_UNIT_CAPTURED:
  630.     unit = find_unit(data0);
  631.     if (unit != NULL) {
  632.         sprintf(buf, "%s", unit_handle(side, unit));
  633.     } else {
  634.          pastunit = find_past_unit(data0);
  635.         if (pastunit != NULL) {
  636.         sprintf(buf, "%s", past_unit_handle(side, pastunit));
  637.         } else {
  638.         sprintf(buf, "%d??", data0);
  639.         }
  640.     }
  641.      tprintf(buf, " captured ");
  642.     unit = find_unit_dead_or_alive(data1);
  643.     if (unit != NULL) {
  644.         tprintf(buf, "%s", unit_handle(side, unit));
  645.     } else {
  646.          pastunit = find_past_unit(data1);
  647.         if (pastunit != NULL) {
  648.         tprintf(buf, "%s", past_unit_handle(side, pastunit));
  649.         } else {
  650.         tprintf(buf, "%d??", data1);
  651.         }
  652.     }
  653.     break;
  654.       case H_UNIT_DAMAGED:
  655.     unit = find_unit_dead_or_alive(data0);
  656.     if (unit != NULL) {
  657.         sprintf(buf, "%s", unit_handle(side, unit));
  658.     } else {
  659.          pastunit = find_past_unit(data0);
  660.         if (pastunit != NULL) {
  661.         sprintf(buf, "%s", past_unit_handle(side, pastunit));
  662.         } else {
  663.         sprintf(buf, "%d??", data0);
  664.         }
  665.     }
  666.     tprintf(buf, " damaged (%d -> %d hp)", data1, hevt->data[2]);
  667.     break;
  668.       case H_UNIT_KILLED:
  669.     pastunit = find_past_unit(data0);
  670.     if (pastunit != NULL) {
  671.         sprintf(buf, "%s", past_unit_handle(side, pastunit));
  672.     } else {
  673.         sprintf(buf, "%d??", data0);
  674.     }
  675.     tprintf(buf, " was destroyed");
  676.     break;
  677.       case H_UNIT_STARVED:
  678.     pastunit = find_past_unit(data0);
  679.     if (pastunit != NULL) {
  680.         sprintf(buf, "%s", past_unit_handle(side, pastunit));
  681.     } else {
  682.         sprintf(buf, "%d??", data0);
  683.     }
  684.     tprintf(buf, " starved to death");
  685.     break;
  686.        case H_UNIT_NAME_CHANGED:
  687.     pastunit = find_past_unit(data0);
  688.     if (pastunit != NULL) {
  689.         sprintf(buf, "%s", past_unit_handle(side, pastunit));
  690.     } else {
  691.         sprintf(buf, "%d??", data0);
  692.     }
  693.     unit = find_unit(data1);
  694.     if (unit != NULL) {
  695.         if (unit->name != NULL)
  696.           tprintf(buf, " changed name to \"%s\"", unit->name);
  697.         else
  698.           tprintf(buf, " became anonymous");
  699.     } else {
  700.     pastunit2 = find_past_unit(data1);
  701.        if (pastunit2 != NULL) {
  702.         if (pastunit2->name != NULL)
  703.           tprintf(buf, " changed name to \"%s\"", pastunit2->name);
  704.         else
  705.           tprintf(buf, " became anonymous");
  706.       }
  707.     }
  708.     break;
  709.       default:
  710.     run_warning("\"%s\" has no description", hevtdefns[hevt->type].name);
  711.     break;
  712.     }
  713. }
  714.  
  715. /* Generate a description of the borders and connections in and around
  716.    a location. */
  717.  
  718. void
  719. linear_desc(buf, x, y)
  720. char *buf;
  721. int x, y;
  722. {
  723.     int t, dir;
  724.  
  725.     if (any_aux_terrain_defined()) {
  726.     for_all_terrain_types(t) {
  727.         if (t_is_border(t)
  728.         && aux_terrain_defined(t)
  729.         && any_borders_at(x, y, t)) {
  730.         tprintf(buf, " %s", t_type_name(t)); 
  731.         for_all_directions(dir) {
  732.             if (border_at(x, y, dir, t)) {
  733.             tprintf(buf, "/%s", dirnames[dir]);
  734.             }
  735.         }
  736.         }
  737.         if (t_is_connection(t)
  738.         && aux_terrain_defined(t)
  739.         && any_connections_at(x, y, t)) {
  740.         tprintf(buf, " %s", t_type_name(t)); 
  741.         for_all_directions(dir) {
  742.             if (connection_at(x, y, dir, t)) {
  743.             tprintf(buf, "/%s", dirnames[dir]);
  744.             }
  745.         }
  746.         }
  747.     }
  748.     }
  749. }
  750.  
  751. void
  752. elevation_desc(buf, x, y)
  753. char *buf;
  754. int x, y;
  755. {
  756.     if (elevations_defined()) {
  757.     sprintf(buf, "(Elev %d)", elev_at(x, y));
  758.     }
  759. }
  760.  
  761. char *
  762. feature_desc(feature, buf)
  763. Feature *feature;
  764. char *buf;
  765. {
  766.     int i, capitalize = FALSE;
  767.     char *str;
  768.  
  769.     if (feature == NULL)
  770.       return NULL;
  771.     if (feature->name) {
  772.     /* Does the name need any substitutions done? */
  773.     if (strchr(feature->name, '%')) {
  774.         i = 0;
  775.         for (str = feature->name; *str != '\0'; ++str) {
  776.             if (*str == '%') {
  777.             /* Interpret substitution directives. */
  778.             switch (*(str + 1)) {
  779.               case 'T':
  780.             capitalize = TRUE;
  781.               case 't':
  782.             if (feature->typename) {
  783.                 buf[i] = '\0';
  784.                 strcat(buf, feature->typename);
  785.                 if (capitalize && islower(buf[i]))
  786.                   buf[i] = toupper(buf[i]);
  787.                 i = strlen(buf);
  788.             } else {
  789.                 /* do nothing */
  790.             }
  791.             ++str;
  792.             break;
  793.               case '\0':
  794.             break;  /* (should be error?) */
  795.               default:
  796.             /* (error?) */
  797.             break;
  798.             }
  799.             } else {
  800.             buf[i++] = *str;
  801.             }
  802.         }
  803.         /* Close off the string. */
  804.         buf[i] = '\0';
  805.         return buf;
  806.     } else {
  807.         /* Return the name alone. */
  808.         return feature->name;
  809.     }
  810.     } else {
  811.     if (feature->typename) {
  812.         sprintf(buf, "unnamed %s", feature->typename);
  813.         return buf;
  814.     }
  815.     }
  816.     /* No description of the location is available. */
  817.     return NULL;
  818. }
  819.  
  820. /* Generate a string describing what is at the given location. */
  821.  
  822. char *featurebuf = NULL;
  823.  
  824. char *
  825. feature_name_at(x, y)
  826. int x, y;
  827. {
  828.     int fid = (features_defined() ? raw_feature_at(x, y) : 0);
  829.     Feature *feature;
  830.  
  831.     if (fid == 0)
  832.       return NULL;
  833.     feature = find_feature(fid);
  834.     if (feature != NULL) {
  835.     if (featurebuf == NULL)
  836.       featurebuf = xmalloc(BUFSIZE);
  837.     return feature_desc(feature, featurebuf);
  838.     }
  839.     /* No description of the location is available. */
  840.     return NULL;
  841. }
  842.  
  843. void
  844. temperature_desc(buf, x, y)
  845. char *buf;
  846. int x, y;
  847. {
  848.     if (temperatures_defined()) {
  849.     sprintf(buf, "(Temp %d)", temperature_at(x, y));
  850.     }
  851. }
  852.  
  853. #if 0
  854.     int age, u;
  855.     short view, prevview;
  856.     Side *side2;
  857.  
  858.     /* Compose and display view history of this cell. */
  859.     Dprintf("Drawing previous view info\n");
  860.     age = side_view_age(side, curx, cury);
  861.     prevview = side_prevview(side, curx, cury);
  862.     if (age == 0) {
  863.     if (prevview != view) {
  864.         if (prevview == EMPTY) {
  865.         /* misleading if prevview was set during init. */
  866.         sprintf(tmpbuf, "Up to date; had been empty.");
  867.         } else if (prevview == UNSEEN) {
  868.         sprintf(tmpbuf, "Up to date; had been unexplored.");
  869.         } else {
  870.         side2 = side_n(vside(prevview));
  871.         u = vtype(prevview);
  872.         if (side2 != side) {
  873.             sprintf(tmpbuf, "Up to date; had seen %s %s.",
  874.                 (side2 == NULL ? "independent" :
  875.                  side_name(side2)),
  876.                 u_type_name(u));
  877.         } else {
  878.             sprintf(tmpbuf,
  879.                 "Up to date; had been occupied by your %s.",
  880.                 u_type_name(u));
  881.         }
  882.         }
  883.     } else {
  884.         sprintf(tmpbuf, "Up to date.");
  885.     }
  886.     } else {
  887.     if (prevview == EMPTY) {
  888.         sprintf(tmpbuf, "Was empty %d turns ago.", age);
  889.     } else if (prevview == UNSEEN) {
  890.         sprintf(tmpbuf, "Terrain first seen %d turns ago.", age);
  891.     } else {
  892.         side2 = side_n(vside(prevview));
  893.         u = vtype(prevview);
  894.         if (side2 != side) {
  895.         sprintf(tmpbuf, "Saw %s %s, %d turns ago.",
  896.             (side2 == NULL ? "independent" :
  897.              side_name(side2)),
  898.             u_type_name(u), age);
  899.         } else {
  900.         sprintf(tmpbuf, "Was occupied by your %s %d turns ago.",
  901.             u_type_name(u), age);
  902.         }
  903.     }
  904.     }
  905. #endif
  906.  
  907. void
  908. hp_desc(buf, unit, label)
  909. char *buf;
  910. Unit *unit;
  911. int label;
  912. {
  913.     if (label) {
  914.     sprintf(buf, "HP ");
  915.     } else {
  916.     sprintf(buf, "");
  917.     }
  918.     /* (print '-' or some such for zero hp case?) */
  919.     if (unit->hp == u_hp(unit->type)) {
  920.     tprintf(buf, "%d", unit->hp);
  921.     } else {
  922.     tprintf(buf, "%d/%d", unit->hp, u_hp(unit->type));
  923.     } 
  924. }
  925.  
  926. void
  927. acp_desc(buf, unit, label)
  928. char *buf;
  929. Unit *unit;
  930. int label;
  931. {
  932.     int u = unit->type;
  933.  
  934.     if (!completed(unit)) {
  935.     sprintf(buf, "%d/%d done", unit->cp, u_cp(u));
  936.     } else if (unit->act && u_acp(u) > 0) {
  937.         if (label) {
  938.             strcpy(buf, "ACP ");
  939.         } else {
  940.             strcpy(buf, "");
  941.         }
  942.     if (unit->act->acp == unit->act->initacp) {
  943.         tprintf(buf, "%d", unit->act->acp);
  944.     } else {
  945.         tprintf(buf, "%d/%d", unit->act->acp, unit->act->initacp);
  946.     }
  947.     } else {
  948.         strcpy(buf, "");
  949.     }
  950. }
  951.  
  952. void
  953. plan_desc(buf, unit)
  954. char *buf;
  955. Unit *unit;
  956. {
  957.     Plan *plan = unit->plan;
  958.     Task *task = plan->tasks;
  959.  
  960.     if (plan == NULL) {
  961.     buf[0] = '\0';
  962.         return;
  963.     }
  964.     sprintf(buf, "%s%s%s%s%s%s%s",
  965.         plantypenames[plan->type],
  966.         (plan->waitingfortasks ? " Waiting" : ""),
  967.         (plan->asleep ? " Asleep" : ""),
  968.         (plan->reserve ? " Reserve" : ""),
  969.         (plan->delayed ? " Delayed" : ""),
  970.         (plan->aicontrol ? " Delegated" : ""),
  971.         (plan->supply_is_low ? " Low" : ""),
  972.         (plan->supply_alarm ? " SAlarm" : "")
  973.         );
  974.     if (task) {
  975.     strcat(buf, " ");
  976.         task_desc(buf+strlen(buf), task);
  977.         if (task->next)
  978.       tprintf(buf, " ...");
  979.     } 
  980. }
  981.  
  982. void
  983. task_desc(buf, task)
  984. char *buf;
  985. Task *task;
  986. {
  987.     int i, slen;
  988.     char *argtypes;
  989.  
  990.     sprintf(buf, "%s", taskdefns[task->type].name);
  991.     switch (task->type) {
  992.       case TASK_BUILD:
  993.     tprintf(buf, " %s", u_type_name(task->args[0]));
  994.     if (task->args[1] != 0) {
  995.         Unit *unit = find_unit(task->args[1]);
  996.  
  997.         if (unit != NULL) {
  998.         tprintf(buf, " (%d cp)", unit->cp);
  999.         }
  1000.     }
  1001.     tprintf(buf, ", %d of %d", task->args[2], task->args[3]);
  1002.     break;
  1003.       case TASK_HIT_POSITION:
  1004.           tprintf(buf, " %d,%d", task->args[0], task->args[1]);
  1005.     break;
  1006.       case TASK_HIT_UNIT:
  1007.           tprintf(buf, " at %d,%d (type %d side %d)",
  1008.               task->args[0], task->args[1], task->args[2], task->args[3]);
  1009.     break;
  1010.       case TASK_MOVETO:
  1011.         if (task->args[2] == 0) {
  1012.         tprintf(buf, " %d,%d", task->args[0], task->args[1]);
  1013.         } else if (task->args[2] == 1) {
  1014.         tprintf(buf, " adjacent %d,%d", task->args[0], task->args[1]);
  1015.         } else {
  1016.         tprintf(buf, " within %d of %d,%d",
  1017.             task->args[2], task->args[0], task->args[1]);
  1018.         }
  1019.     break;
  1020.       default:
  1021.     argtypes = taskdefns[task->type].argtypes;
  1022.     slen = strlen(argtypes);
  1023.     for (i = 0; i < slen; ++i) {
  1024.         tprintf(buf, "%c%d", (i == 0 ? ' ' : ','), task->args[i]);
  1025.     }
  1026.     break;
  1027.     }
  1028.     tprintf(buf, " x %d", task->execnum);
  1029.     if (task->retrynum > 0) {
  1030.     tprintf(buf, " fail %d", task->retrynum);
  1031.     }
  1032. }
  1033.  
  1034. /* Format a clock time into a standard form.  This routine will omit the hours
  1035.    part if it will be uninteresting. */
  1036.  
  1037. void
  1038. time_desc(buf, time, maxtime)
  1039. char *buf;
  1040. int time, maxtime;
  1041. {
  1042.     int hour, minute, second;
  1043.  
  1044.     if (time >= 0) {
  1045.     hour = time / 3600;  minute = (time / 60) % 60;  second = time % 60;
  1046.         if (between(1, maxtime, 3600) && hour == 0) {
  1047.         sprintf(buf, "%.2d:%.2d", minute, second);
  1048.     } else {
  1049.         sprintf(buf, "%.2d:%.2d:%.2d", hour, minute, second);
  1050.     }
  1051.     } else {
  1052.         sprintf(buf, "??:??:??");
  1053.     }
  1054. }
  1055.  
  1056. /* General-purpose routine to take an array of anonymous unit types and
  1057.    summarize what's in it, using numbers and unit chars. */
  1058.  
  1059. char *
  1060. summarize_units(buf, ucnts)
  1061. char *buf;
  1062. int *ucnts;
  1063. {
  1064.     char tmp[BUFSIZE];  /* should be bigger? */
  1065.     int u;
  1066.  
  1067.     sprintf(buf, "");
  1068.     for_all_unit_types(u) {
  1069.     if (ucnts[u] > 0) {
  1070.         sprintf(tmp, " %d %s", ucnts[u], utype_name_n(u, 3));
  1071.         strcat(buf, tmp);
  1072.     }
  1073.     }
  1074.     return buf;
  1075. }
  1076.  
  1077. /* Compose a one-line comment on the game. */
  1078. /* (should reindent) */
  1079. char *
  1080. exit_commentary(side)
  1081. Side *side;
  1082. {
  1083.     int numingame = 0, numwon = 0, numlost = 0;
  1084.     Side *side2, *lastside, *winner, *loser;
  1085.  
  1086.     for_all_sides(side2) {
  1087.     if (side2->ingame) {
  1088.         ++numingame;
  1089.         lastside = side2;
  1090.     }
  1091.     if (side_won(side2)) {
  1092.         ++numwon;
  1093.         winner = side2;
  1094.     }
  1095.     if (side_lost(side2)) {
  1096.         ++numlost;
  1097.         loser = side2;
  1098.     }
  1099.     }
  1100.     if (numingame > 0) {
  1101.         if (0 /* could have been resolved, need to check scorekeepers */) {
  1102.             sprintf(spbuf, "The outcome remains undecided");
  1103. #ifdef RUDE
  1104.             if (numsides > 1) {
  1105.                 strcat(spbuf, ", but you're probably the loser!");
  1106.             } else {
  1107.                 strcat(spbuf, "...");
  1108.             }
  1109. #else
  1110.             strcat(spbuf, "...");
  1111. #endif /* RUDE */
  1112.         } else {
  1113.             sprintf(spbuf, "Game is over.");
  1114.         }
  1115.     } else if (numwon == numsides) {
  1116.         sprintf(spbuf, "Everybody won!");
  1117.     } else if (numlost == numsides) {
  1118.         sprintf(spbuf, "Everybody lost!");
  1119.     } else if (numwon == 1) {
  1120.         sprintf(spbuf, "%s won!", (side == winner ? "You" : side_desig(winner)));
  1121.     } else if (numlost == 1) {
  1122.         sprintf(spbuf, "%s lost!", (side == loser ? "You" : side_desig(loser)));
  1123.     } else {
  1124.         sprintf(spbuf, "Some won and some lost.");
  1125.     }
  1126.     return spbuf;
  1127. }
  1128.  
  1129. #if 0
  1130.     /* (get from nlang.c?) */
  1131.     notify_all(
  1132. #ifdef RUDE
  1133.            "Those %s %s have given up!",
  1134.            (flip_coin() ? "cowardly" : "wimpy"),
  1135. #else
  1136.            "The %s have given up!",
  1137. #endif /* RUDE */
  1138.            side->pluralnoun);
  1139.     if (side2 != NULL) {
  1140.     notify_all("... and they're giving all their stuff to the %s!",
  1141.            side2->pluralnoun);
  1142.     }
  1143. #endif
  1144.  
  1145. /* The following routines should probably go into some kind of international
  1146.    type interface. */
  1147.  
  1148. /* Given a number, figure out what suffix should go with it. */
  1149.  
  1150. char *
  1151. ordinal_suffix(n)
  1152. int n;
  1153. {
  1154.     if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) {
  1155.     return "th";
  1156.     } else {
  1157.     switch (n % 10) {
  1158.       case 1:   return "st";
  1159.       case 2:   return "nd";
  1160.       case 3:   return "rd";
  1161.       default:  return "th";
  1162.     }
  1163.     }
  1164. }
  1165.  
  1166. /* Pluralize a word, attempting to be smart about various possibilities
  1167.    that don't have a different plural form (such as "Chinese" and "Swiss"). */
  1168.  
  1169. /* There should probably be a test for when to add "es" instead of "s". */
  1170.  
  1171. char *pluralbuf = NULL;
  1172.  
  1173. char *
  1174. plural_form(word)
  1175. char *word;
  1176. {
  1177.     char endch = ' ', nextend = ' ';
  1178.  
  1179.     if (pluralbuf == NULL)
  1180.       pluralbuf = xmalloc(BUFSIZE);
  1181.     if (word == NULL) {
  1182.     run_warning("plural_form given NULL string");
  1183.     pluralbuf[0] = '\0';
  1184.     return pluralbuf;
  1185.     }
  1186.     if (strlen(word) > 0)
  1187.       endch   = word[strlen(word)-1];
  1188.     if (strlen(word) > 1)
  1189.       nextend = word[strlen(word)-2];
  1190.     if (endch == 'h' || endch == 's' || (endch == 'e' && nextend == 's')) {
  1191.     sprintf(pluralbuf, "%s", word);
  1192.     } else {
  1193.     sprintf(pluralbuf, "%ss", word);
  1194.     }
  1195.     return pluralbuf;
  1196. }
  1197.  
  1198. /* General text generation. */
  1199.  
  1200. /*
  1201. Need some sort of default grammar used when game designs don't want
  1202. to add anything special.
  1203. Copy ^0, ... concept for format strings generated by grammars.
  1204. Use grammar idea for narrative generation.
  1205. Do for actions, notable backdrop events, summaries.
  1206. (text disband-narrative
  1207.   (self infantry "%2 goes home")
  1208.   (u* bomb "%1 dismantles %2")
  1209.   )
  1210. Match on action args to choose sentence, allow multiple weighted choices
  1211. for variety, be able to tailor for each side.
  1212. Should be able to do both present and past tense generation.
  1213. Routine is describe_thing(side(s), generator, parms[10]).
  1214. (Would be generally useful to have a side-to-bit-vector conversion routine...)
  1215. "Narrative" different from "message", is past sense, while "message"
  1216. describes ongoing things.
  1217. Need to do narrative descriptions / events for notable backdrop events
  1218. such as migrations, storms, etc.
  1219. */
  1220.  
  1221. char *
  1222. make_text(buf, maker, a1, a2, a3, a4)
  1223. char *buf;
  1224. Obj *maker;
  1225. long a1, a2, a3, a4;
  1226. {
  1227.     if (buf == NULL)
  1228.       buf = xmalloc(BUFSIZE);
  1229.     if (maker != lispnil) {
  1230.     } else {
  1231.     sprintf(buf, "%d %d %d %d", a1, a2, a3, a4);
  1232.     }
  1233.     return buf;
  1234. }
  1235.  
  1236. /* Date/time parsing and formatting. */
  1237.  
  1238. /* Compose a readable form of the given date. */
  1239.  
  1240. char *
  1241. absolute_date_string(date)
  1242. long date;
  1243. {
  1244.     int caltypeunknown = FALSE;
  1245.     Obj *cal, *caltype, *stepname, *step;
  1246.  
  1247.     if (datebuf == NULL)
  1248.       datebuf = xmalloc(BUFSIZE);
  1249.     /* The first time we ask for a date, interpret the calendar. */
  1250.     if (calendartype < 0) {
  1251.         cal = g_calendar();
  1252.         if (consp(cal)) {
  1253.             caltype = car(cal);
  1254.             if ((symbolp(caltype) || stringp(caltype))
  1255.                 && strcmp("usual", c_string(caltype)) == 0) {
  1256.                 calendartype = 1;
  1257.             stepname = cadr(cal);
  1258.             datestepname = (stringp(stepname) ? c_string(stepname) : "day");
  1259.             step = car(cddr(cal));
  1260.             datestep = (numberp(step) ? c_number(step) : 1);
  1261.             } else {
  1262.                 caltypeunknown = TRUE;
  1263.             }
  1264.         } else {
  1265.             calendartype = 0;
  1266.             turnname = "Turn";
  1267.             if (stringp(cal)) {
  1268.                 turnname = c_string(cal);
  1269.             } else if (cal != lispnil) {
  1270.                 caltypeunknown = TRUE;
  1271.             }
  1272.     }
  1273.     if (caltypeunknown) {
  1274.         init_warning("Unknown calendar type");
  1275.     }
  1276.     }
  1277.     switch (calendartype) {
  1278.       case 0:
  1279.     sprintf(datebuf, "%s%4d", turnname, date);
  1280.     return datebuf;
  1281.       case 1:
  1282.     return usual_date_string(date);
  1283.       default:
  1284.     case_panic("calendar type", calendartype);
  1285.     }
  1286. }
  1287.  
  1288. /* Compose a date, omitting components supplied by the base date. */
  1289.  
  1290. char *
  1291. relative_date_string(date, base)
  1292. long date, base;
  1293. {
  1294.     if (datebuf == NULL)
  1295.       datebuf = xmalloc(BUFSIZE);
  1296.     /* should do this for real eventually */
  1297.     sprintf(datebuf, "%d(%d)", date, base);
  1298.     return datebuf;
  1299. }
  1300.  
  1301. void
  1302. parse_usual_initial_date()
  1303. {
  1304.     char *date = g_base_date();
  1305.     char monthname[BUFSIZE];
  1306.     int i;
  1307.  
  1308.     if (baseday < 0) {
  1309.     if (!empty_string(date)) {
  1310.         /* Assume it's in a standard date format. */
  1311.         if (strcmp(datestepname, "hour") == 0) {
  1312.         sscanf(date, "%d %d %s %d",
  1313.                &basehour, &baseday, monthname, &baseyear);
  1314.         --baseday;
  1315.         } else if (strcmp(datestepname, "day") == 0) {
  1316.         sscanf(date, "%d %s %d", &baseday, monthname, &baseyear);
  1317.         --baseday;
  1318.         } else if (strcmp(datestepname, "week") == 0) {
  1319.         sscanf(date, "%d %s %d", &baseday, monthname, &baseyear);
  1320.         --baseday;
  1321.         } else if (strcmp(datestepname, "month") == 0) {
  1322.         sscanf(date, "%s %d", monthname, &baseyear);
  1323.         } else if (strcmp(datestepname, "year") == 0) {
  1324.         sscanf(date, "%d", &baseyear);
  1325.         }
  1326.         for (i = 0; i < 12; ++i) {
  1327.         if (strcmp(monthname, months[i]) == 0) {
  1328.             basemonth = i;
  1329.             return;
  1330.         }
  1331.         }
  1332.     } else {
  1333.         baseday = 0;
  1334.     }
  1335.     }
  1336. }
  1337.  
  1338.  
  1339. /* Given a numeric data, convert it into something understandable.  Depends
  1340.    on the length of a turn. */
  1341.  
  1342. static char *
  1343. usual_date_string(date)
  1344. int date;
  1345. {
  1346.     int year = 0, season = 0, month = 0, week = 0, day = 0;
  1347.     int hour = 0, second = 0, minute = 0;
  1348.  
  1349.     /* The date, which is a turn number, should be 1 or more, but this
  1350.        routine may be called before the game really starts, so return
  1351.        something that will be distinctive if it's ever displayed. */
  1352.     if (date <= 0)
  1353.       return "?date<=0?";
  1354.     /* First displayed date is normally turn 1; be zero-based for
  1355.        the benefit of calculation. */
  1356.     --date;
  1357.     date *= datestep;
  1358.     parse_usual_initial_date();
  1359.     if (strcmp(datestepname, "second") == 0) {
  1360.     second = date % 60;
  1361.     minute = date / 60;
  1362.     sprintf(datebuf, "%d:%d", minute, second);
  1363.     } else if (strcmp(datestepname, "minute") == 0) {
  1364.     minute = date % 60;
  1365.     hour = date / 60 + basehour;
  1366.     sprintf(datebuf, "%d:%d", hour, minute);
  1367.     } else if (strcmp(datestepname, "hour") == 0) {
  1368.      hour = (date + basehour) % 24;
  1369.     /* Convert to days, then proceed as for days. */
  1370.      date = (date + basehour) / 24;
  1371.     date += 30 * basemonth;
  1372.     day = date % 365;
  1373.     month = day / 30;
  1374.     day = (day + baseday) % 30 + 1;
  1375.     year = date / 365 + baseyear;
  1376.     sprintf(datebuf, "%d:00 %2d %s %d", hour, day, months[month], ABS(year));
  1377.    } else if (strcmp(datestepname, "day") == 0) {
  1378.     /* Should do this more accurately... */
  1379.     date += 30 * basemonth;
  1380.     day = date % 365;
  1381.     month = day / 30;
  1382.     day = (day + baseday) % 30 + 1;
  1383.     year = date / 365 + baseyear;
  1384.     sprintf(datebuf, "%2d %s %d", day, months[month], ABS(year));
  1385.     } else if (strcmp(datestepname, "week") == 0) {
  1386.     /* Convert to days, then proceed as for days. */
  1387.     date *= 7;
  1388.     date += 30 * basemonth;
  1389.     day = date % 365;
  1390.     month = day / 30;
  1391.     day = (day + baseday) % 30 + 1;
  1392.     year = date / 365 + baseyear;
  1393.     sprintf(datebuf, "%2d %s %d", day, months[month], ABS(year));
  1394.     } else if (strcmp(datestepname, "month") == 0) {
  1395.         month = date % 12;
  1396.     year = date / 12 + baseyear;
  1397.     sprintf(datebuf, "%s %d", months[month], ABS(year));
  1398.     } else if (strcmp(datestepname, "season") == 0) {
  1399.         season = date % 4;
  1400.     year = date / 4 + baseyear;
  1401.     sprintf(datebuf, "%d %d", season, ABS(year));
  1402.     } else if (strcmp(datestepname, "year") == 0) {
  1403.     year = date + baseyear;
  1404.     sprintf(datebuf, "%d", ABS(year));
  1405.     } else {
  1406.     sprintf(datebuf, "?what's a %s?", datestepname);
  1407.     }
  1408.     if (year < 0) {
  1409.     strcat(datebuf, " BC");
  1410.     }
  1411.     return datebuf;
  1412. }
  1413.  
  1414. /* Show some overall numbers on performance of a side. */
  1415.  
  1416. void
  1417. write_side_results(fp, side)
  1418. FILE *fp;
  1419. Side *side;
  1420. {
  1421.     if (side == NULL) {
  1422.     fprintf(fp, "Results for game as a whole:\n\n");
  1423.     } else {
  1424.     fprintf(fp, "Results for %s, played by %s:\n\n",
  1425.         short_side_title(side), long_player_title(spbuf, side->player, NULL));
  1426.     /* (should mention win/loss and such) */
  1427.     }
  1428. }
  1429.  
  1430. /* Display what is essentially a double-column bookkeeping of unit gains
  1431.    and losses. */
  1432.  
  1433. /* (should reindent) */
  1434. void
  1435. write_unit_record(fp, side)
  1436. FILE *fp;
  1437. Side *side;
  1438. {
  1439.     int u, gainreason, lossreason, totgain, totloss, val;
  1440.  
  1441.     fprintf(fp, "Unit Record (gains and losses by cause and unit type)\n");
  1442.     fprintf(fp, " Unit Type ");
  1443.     for (gainreason = 0; gainreason < NUMGAINREASONS; ++gainreason) {
  1444.     fprintf(fp, " %3s", gain_reason_names[gainreason]);
  1445.     }
  1446.     fprintf(fp, " Gain |");
  1447.     for (lossreason = 0; lossreason < NUMLOSSREASONS; ++lossreason) {
  1448.     fprintf(fp, " %3s", loss_reason_names[lossreason]);
  1449.     }
  1450.     fprintf(fp, " Loss |");
  1451.     fprintf(fp, " Total\n");
  1452.     for_all_unit_types(u) {
  1453.       if (1 /* type was at least potentially in game */) {
  1454.     totgain = 0;
  1455.     fprintf(fp, " %9s ", utype_name_n(u, 9));
  1456.     for (gainreason = 0; gainreason < NUMGAINREASONS; ++gainreason) {
  1457.         val = gain_count(side, u, gainreason);
  1458.         if (val > 0) {
  1459.         fprintf(fp, " %3d", val);
  1460.         totgain += val;
  1461.         } else {
  1462.         fprintf(fp, "    ");
  1463.         }
  1464.     }
  1465.     fprintf(fp, "  %3d |", totgain);
  1466.     totloss = 0;
  1467.     for (lossreason = 0; lossreason < NUMLOSSREASONS; ++lossreason) {
  1468.         val = loss_count(side, u, lossreason);
  1469.         if (val > 0) {
  1470.         fprintf(fp, " %3d", val);
  1471.         totloss += val;
  1472.         } else {
  1473.         fprintf(fp, "    ");
  1474.         }
  1475.     }
  1476.     fprintf(fp, "  %3d |", totloss);
  1477.     fprintf(fp, "  %3d\n", totgain - totloss);
  1478.       }
  1479.     }
  1480.     fprintf(fp, "\n");
  1481. }
  1482.  
  1483. static int
  1484. gain_count(side, u, r)
  1485. Side *side;
  1486. int u, r;
  1487. {
  1488.     int sum;
  1489.  
  1490.     if (side != NULL)
  1491.       return side_gain_count(side, u, r);
  1492.     sum = 0;
  1493.     for_all_sides(side) {
  1494.     sum += side_gain_count(side, u, r);
  1495.     }
  1496.     return sum;
  1497. }
  1498.  
  1499. static int
  1500. loss_count(side, u, r)
  1501. Side *side;
  1502. int u, r;
  1503. {
  1504.     int sum;
  1505.  
  1506.     if (side != NULL)
  1507.       return side_loss_count(side, u, r);
  1508.     sum = 0;
  1509.     for_all_sides(side) {
  1510.     sum += side_loss_count(side, u, r);
  1511.     }
  1512.     return sum;
  1513. }
  1514.  
  1515. /* Nearly-raw combat statistics; hard to interpret, but they provide
  1516.    a useful check against subjective evaluation of performance. */
  1517. /* (should reindent) */
  1518. void
  1519. write_combat_results(fp, side)
  1520. FILE *fp;
  1521. Side *side;
  1522. {
  1523.     int a, d, atk;
  1524.  
  1525.     fprintf(fp,
  1526.         "Unit Combat Results (average damage over # attacks against enemy, by type)\n");
  1527.     fprintf(fp, " A  D->");
  1528.     for_all_unit_types(d) {
  1529.       if (1 /* part of game */) {
  1530.     fprintf(fp, " %4s ", utype_name_n(d, 4));
  1531.       }
  1532.     }
  1533.     fprintf(fp, "\n");
  1534.     for_all_unit_types(a) {
  1535.       if (1 /* part of game */) {
  1536.     fprintf(fp, " %4s ", utype_name_n(a, 4));
  1537.     for_all_unit_types(d) {
  1538.       if (1 /* part of game */) {
  1539.         atk = atkstats(side, a, d);
  1540.         if (atk > 0) {
  1541.         fprintf(fp, " %5.2f",
  1542.             ((float) hitstats(side, a, d)) / atk);
  1543.         } else {
  1544.         fprintf(fp, "      ");
  1545.         }
  1546.       }
  1547.     }
  1548.     fprintf(fp, "\n     ");
  1549.     for_all_unit_types(d) {
  1550.       if (1 /* part of game */) {
  1551.         atk = atkstats(side, a, d);
  1552.         if (atk > 0) {
  1553.         fprintf(fp, " %4d ", atk);
  1554.         } else {
  1555.         fprintf(fp, "      ");
  1556.         }
  1557.       }
  1558.     }
  1559.     fprintf(fp, "\n");
  1560.       }
  1561.     }
  1562.     fprintf(fp, "\n");
  1563. }
  1564.  
  1565. static int
  1566. atkstats(side, a, d)
  1567. Side *side;
  1568. int a, d;
  1569. {
  1570.     int sum;
  1571.  
  1572.     if (side != NULL)
  1573.       return side_atkstats(side, a, d);
  1574.     sum = 0;
  1575.     for_all_sides(side) {
  1576.     sum += side_atkstats(side, a, d);
  1577.     }
  1578.     return sum;
  1579. }
  1580.  
  1581. static int
  1582. hitstats(side, a, d)
  1583. Side *side;
  1584. int a, d;
  1585. {
  1586.     int sum;
  1587.  
  1588.     if (side != NULL)
  1589.       return side_hitstats(side, a, d);
  1590.     sum = 0;
  1591.     for_all_sides(side) {
  1592.     sum += side_hitstats(side, a, d);
  1593.     }
  1594.     return sum;
  1595. }
  1596.  
  1597. void
  1598. dice_desc(buf, dice)
  1599. char *buf;
  1600. int dice;
  1601. {
  1602.     int numdice, die, offset;
  1603.  
  1604.     if (dice >> 14 == 0 || dice >> 14 == 3) {
  1605.     sprintf(buf, "%d", dice);
  1606.     } else {
  1607.         numdice = (dice >> 11) & 0x07;
  1608.         die = (dice >> 7) & 0x0f;
  1609.         offset = dice & 0x7f;
  1610.         if (offset == 0) {
  1611.         sprintf(buf, "%dd%d", numdice, die);
  1612.         } else {
  1613.         sprintf(buf, "%dd%d+%d", numdice, die, offset);
  1614.         }
  1615.     }
  1616. }
  1617.  
  1618. /* The following code formats a list of types that are missing images. */
  1619.  
  1620. void
  1621. record_missing_image(typtyp, str)
  1622. int typtyp;
  1623. char *str;
  1624. {
  1625.     if (missinglist == NULL) {
  1626.     missinglist = xmalloc(BUFSIZE);
  1627.     missinglist[0] = '\0';
  1628.     }
  1629.     ++missing[typtyp];
  1630.     /* Add the name of the image-less type, but only if one of
  1631.        the first few. */
  1632.     if (between(1, totlisted, NUMTOLIST))
  1633.       strcat(missinglist, ",");
  1634.     if (totlisted < NUMTOLIST) {
  1635.     strcat(missinglist, str);
  1636.     } else if (totlisted == NUMTOLIST) {
  1637.     strcat(missinglist, "...");
  1638.     }
  1639.     ++totlisted;
  1640. }
  1641.  
  1642. /* Return true if any images could not be found, and provide some helpful info
  1643.    into the supplied buffer. */
  1644.  
  1645. int
  1646. missing_images(buf)
  1647. char *buf;
  1648. {
  1649.     if (missinglist == NULL)
  1650.       return FALSE;
  1651.     buf[0] = '\0';
  1652.     if (missing[UTYP] > 0)
  1653.       tprintf(buf, " %d unit images", missing[UTYP]);
  1654.     if (missing[TTYP] > 0)
  1655.       tprintf(buf, " %d terrain images", missing[TTYP]);
  1656.     if (missing[3] > 0)
  1657.       tprintf(buf, " %d emblems", missing[3]);
  1658.     tprintf(buf, " - %s", missinglist);
  1659.     return TRUE;
  1660. }
  1661.  
  1662.